import os, cgi, re
from google.appengine.ext.webapp import template
import cgi, urllib, urllib2, gzip,StringIO, time, datetime
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from xml.dom import minidom
import xml.parsers.expat
from google.appengine.runtime import apiproxy_errors
from google.appengine.runtime import DeadlineExceededError
import browscap

def getxmltv(source='dstv'):
    if source == 'dstv':
        filename='zaxmltv.xml.gz'
        url = 'http://zaxmltv.flash.za.org/schedule/zaxmltv_get.php'
        params = urllib.urlencode({
            'chanlist[0]': 131,
            'chanlist[1]': 132,
            'chanlist[2]': 133,
            'chanlist[3]': 134,
            'download': 'on'})
        result = urlfetch.fetch(url=url,
                            payload=params,
                            method=urlfetch.POST,
                            headers={'Content-Type': 'application/x-www-form-urlencoded'})
        if result.status_code == 200:
            sio = StringIO.StringIO(result.content)
            zf = gzip.GzipFile(filename=filename, mode='rb', fileobj=sio)
            xmlfile = zf.read()
            zf.close()
        else:
            xmlfile = ''
    if source == 'toptv':
        filename='toptv_xmltv.xml'
        url = 'http://bluegray.co.za/xmltv/toptv/index.py'
        params = urllib.urlencode({
            'channel': (25, 26, 27, 4),
            'days': 1}, True)
        result = urlfetch.fetch(url=url,
                            payload=params,
                            method=urlfetch.POST,
                            headers={'Content-Type': 'application/x-www-form-urlencoded'})
        if result.status_code == 200:
            xmlfile = result.content
        else:
            xmlfile = ''
    return xmlfile

def getxmltvlocal():
    filename='zaxmltv.xml.gz'
    zf = gzip.open(filename=filename, mode='rb')
    xmlfile = zf.read()
    zf.close()
    return xmlfile


def timeconv(s):
    s = s[:-6]
    tuple =  time.strptime(s, '%Y%m%d%H%M%S')
    text = time.strftime('%a %H:%M', tuple)
    seconds = time.mktime(tuple)
    return text, seconds, tuple

def str2datetime(s):
    s = s[:-6]
    dt = datetime.datetime.strptime(s, '%Y%m%d%H%M%S')
    return dt

def msg_page(message, instance):
    template_values = {
      'message': message,
      }
    path = os.path.join(os.path.dirname(__file__), 'templates/message.html')
    instance.response.out.write(template.render(path, template_values))

class Program(db.Model):
    channel = db.StringProperty()
    title = db.StringProperty()
    description = db.StringProperty(multiline=True)
    start = db.DateTimeProperty()
    stop = db.DateTimeProperty()
    rating = db.StringProperty()
    lastupdated = db.DateTimeProperty()
    #k = db.StringProperty()

class Channel(db.Model):
    id = db.StringProperty()
    icon = db.StringProperty()
    displayname = db.StringProperty()

class Programs(db.Model):
    program = db.ReferenceProperty(Program)

class LastUpdate(db.Model):
    lastupdated = db.DateTimeProperty()

class Count(db.Model):
    count = db.IntegerProperty()

class UserHighlight(db.Model):
    highlightlist = db.StringListProperty()
    user = db.UserProperty(required=True)

def channelconv(keyname):
    icon = displayn = None
    c = Channel.get_by_key_name(keyname)
    if c:
        icon = c.icon
        displayn = c.displayname
    if displayn.lower() in ['sabc 1', 'sabc 2', 'sabc 3', 'e-tv']:
        return '<div class="channel %s" title="%s" alt="%s" ><h2 class="element-invisible">%s</h2></div>'%(displayn.lower().replace(' ', '_'), displayn, displayn, displayn)
    if icon and displayn:
        return '<img class="channel" title="%s" alt="%s" src="%s" />'%(displayn, displayn, icon)
    elif displayn:
        return '<span>%s</span>'%(displayn)
    else:
        return '<span>%s</span>'%(keyname)


def get_lastupdated():
    keyname = "lastupdatedate"
    lastu = LastUpdate.get_or_insert(keyname, lastupdated=datetime.datetime(1970,1,1))
    lu = lastu.lastupdated
    #lu = Program().all().filter('channel =', 131).order('-lastupdated')[0].lastupdated
    #lu = db.get(Program().all(keys_only=True).order('-lastupdated')[0]).lastupdated
    #lu = db.get(Program().all(keys_only=True).order('-lastupdated')[0]).lastupdated
    #lu = datetime.datetime.utcnow()
    #datetime.datetime(1977,1,1)
    return lu

def searchDB(search='', clss='search'):
        current_count = 0
        programs = []
        query = Program().all().filter('lastupdated =', get_lastupdated()).order('__key__')
        if not isinstance(search, list):
            search = [search]
        while current_count % 1000 == 0:
            current_count = query.count()
            #count += current_count

            for x in query:
                for st in search:
                    if st.lower() in x.title.lower():
                        duration = (x.stop-x.start).seconds/60
                        k = x.key()
                        programs.append({'class': clss,
                            'channel': channelconv(x.channel),
                            'title': x.title,
                            'sort': x.start.isoformat(),
                            'start': x.start.strftime('%a&nbsp;%d/%m&nbsp;%H:%M'),
                            'stop': '%dmin'%duration,
                            'desc': x.description,
                            'rating': x.rating,
                            'key': k,
                            })
            if current_count == 1000:
                last_key = query.fetch(1, 999)[0].key()
                query = query.filter('__key__ > ', last_key)
        return programs


def getfromDB(search='', prevh=1, nexth=2, dayh=0, highlight=[]):
    programs = []
    notadded = []
    #now = time.mktime(datetime.datetime.utcnow().timetuple())
    ten  = datetime.timedelta(minutes=10)
    soon = datetime.timedelta(hours=1)
    next = datetime.timedelta(hours=nexth)
    prev = datetime.timedelta(hours=prevh)
    # hack to make sure whats now showing is included
    hack = datetime.timedelta(hours=5)
    tzoffset = datetime.timedelta(hours=2)
    dayoffset = datetime.timedelta(hours=24*dayh)
    now = datetime.datetime.utcnow() + tzoffset + dayoffset
    #now = datetime.datetime(2009,9,3,22,31) + tzoffset
    #p.order("start")

#    gg = p.filter("stop >", now)
#    gg.filter('stop <=', now)
    lu = get_lastupdated()
    if not search:
        q = Program().all().filter('lastupdated =', lu).filter("start >", now-prev-hack).filter("start <", now+next)
        try:
            for x in q:
                if x.start and x.stop and x.title and x.channel:
                    clss = ''
                    duration = (x.stop-x.start).seconds/60
                    k = x.key()
                    if now-prev < x.stop < now:
                        clss = 'prev'
                    elif now <= x.start < now+ten:
                        clss = 'ten'
                    elif now <= x.start < now+soon:
                        clss = 'soon'
                    elif now <= x.start < now+next:
                        clss = 'next'
                    elif x.stop > now and x.start < now:
                        clss = 'now'
                    for st in highlight:
                        if st.lower() in x.title.lower():
                            if clss:
                                clss += ' highlight'

                    r = re.search('^.*\((\d*?)\)(.*)$', x.description)
                    if r:
                        date = r.group(1)
                        director = r.group(2)
                        if clss:
                            clss += ' movie'

                    prog = {
                        'class': clss,
                        'channel': channelconv(x.channel),
                        'title': x.title,
                        'sort': x.start.isoformat(),
                        'start': x.start.strftime('%a&nbsp;%H:%M'),
                        'stop': '%dmin'%duration,
                        'desc': x.description,
                        'rating': x.rating,
                        'key': k,
                        }
                    if clss:
                        programs.append(prog)
        except db.BadValueError, e:
            print 'Content-Type: text/plain; charset=utf-8\n'
            print e
            exit()

    else:
      programs = searchDB(search)

    return programs, lu

def getdesc(key=''):
    q = Program().all().filter("__key__ =", db.Key(key))
    desc = q[0].description
    titl = q[0].title
    return desc, titl

def countDB(model):
        keyname = "count"
        cc = Count.get_or_insert(keyname, count=9999999)
        count = cc.count
        #query = model.all(keys_only=True).order('__key__')

        #while count % 1000 == 0:
        #    current_count = query.count()
        #    count += current_count

        #    if current_count == 1000:
        #        last_key = query.fetch(1, 999)[0]
        #        query = query.filter('__key__ > ', last_key)
        return count


def cleanDB():
        #clean up
        deleted = numProgramsbefore = numProgramsafter = count = 0
        #now = datetime.datetime.utcnow()
        #d = datetime.timedelta(hours=6)
#        p = Program.gql("WHERE start < :1", now - d)
        #p = Program().all(keys_only=True).order('__key__')
        p = Program().all(keys_only=True).filter('lastupdated <', get_lastupdated()) # returns maximum 1000
        #cc = 500
        #while cc >= 500:
        #    cc = p.count()
        #    if cc >= 500:
        #        last_key = p[1000]
        #        p.filter('__key__ <= ', last_key)
        #        results = p.fetch(10)
        #        db.delete(results)
        #    else:
        #        results = p.fetch(10)
        #        db.delete(results)
#        db.delete(results)
        count = countDB(Program())
        #for x in p:
        #    x.delete()
        #    deleted += 1
        numProgramsbefore = p.count()
        results = p.fetch(500)
        db.delete(results)
        numProgramsafter = p.count()
        deleted = 500
        return deleted, numProgramsbefore, numProgramsafter, count

def putinDB():
#    xmlstr = getxmltv('toptv')
    xmlstr = getxmltv()
    #xmlstr = getxmltvlocal()
    try:
        xmldoc = minidom.parseString(xmlstr)
    except xml.parsers.expat.ExpatError, e:
        xmldoc = None
        print e
    except NameError, e:
        xmldoc = None
        print e
    count = 0
    deleted = 0
    if xmldoc:
        programs = xmldoc.getElementsByTagName('programme')
        channels = xmldoc.getElementsByTagName('channel')
        channellist = []
        for channel in channels:
            icon = channel.getElementsByTagName('icon')[0].attributes['src'].value
            id = channel.attributes['id'].value
            displayname = channel.getElementsByTagName('display-name')[0].firstChild.data.strip()
            c = Channel(key_name=id)
            c.id = id
            c.icon = icon
            c.displayname = displayname
            channellist.append(c)
        db.put(channellist)

        programlist = []
        now = datetime.datetime.utcnow()
        for program in programs:
            try:
                title = program.getElementsByTagName('title')[0].firstChild.data
            except:
                title = None
            channel = program.attributes['channel'].value
            try:
                description = program.getElementsByTagName('desc')[0].firstChild.data
            except:
                description = None
            start = str2datetime(program.attributes['start'].value)
            stop = str2datetime(program.attributes['stop'].value)
            if program.getElementsByTagName('rating'):
                rating = program.getElementsByTagName('rating')[0].getElementsByTagName('value')[0].firstChild.data
            else:
                rating = None

            #programlist.append({
            #    'title': title,
            #    'channel': channel,
            #    'description': description,
            #    'start': start,
            #    'stop': stop,
            #    })
        #for x in programlist:
            p = Program()
            p.title = title
            p.channel = channel
            p.description = description
            p.start = start
            p.stop = stop
            p.rating = rating
            p.lastupdated = now
            programlist.append(p)
            #count += 1
        s = 200
        for x in range(0, len(programlist)/s):
            d = db.put(programlist[x*s:(x+1)*s])
            count += len(d)
        d = db.put(programlist[len(programlist)/s*s:])
        count += len(d)

        keyname = "lastupdatedate"
        lu = LastUpdate.get_or_insert(keyname, lastupdated=now)
        lu.lastupdated = now
        lu.put()
        keyname = "count"
        cc = Count.get_or_insert(keyname, count=count)
        cc.count = count
        cc.put()
    return count, deleted

def get_hl_list(user=None):
    hl = []
    if user:
        r = UserHighlight.get_by_key_name(user.email())
        if r: hl = r.highlightlist
    return hl

#def update_browscap():
#    browscap.update()

def check_browscap(ua):
    mobile = True
    supports_javascript = True
    features = 'mobile[-] js[-]'
    useragent = ua
    bc = browscap.BrowserCapabilities()
    b = bc(useragent)
    if b:
        mobile = b.is_mobile()
        supports_javascript = b.supports_javascript()
        features = 'mobile[%s] js[%s]' %(mobile, supports_javascript)
    return mobile, supports_javascript, features


class MainPage(webapp.RequestHandler):
  def get(self):

    user = users.get_current_user()

    if user:
    #  self.response.headers['Content-Type'] = 'text/plain'
    #  self.response.out.write('Hello, ' + user.nickname())
        uname = user.nickname()
        uemail = user.email()
        ulink = users.create_logout_url(self.request.uri).replace('&', '&amp;')
    else:
        uname = ''
        ulink = users.create_login_url(self.request.uri).replace('&', '&amp;')
    #  self.redirect(users.create_login_url(self.request.uri))

    hl = get_hl_list(user)

    #if uname == 'bluegraydragon':
    #    hl = ['californication', 'race']
    #else:
    #    hl = []


    latest = get_lastupdated()
    if latest < datetime.datetime.utcnow() - datetime.timedelta(hours=1*24):
        try:
            putinDB()
        except:
            pass # Silently ignore errors for auto update
    if self.request.get('prev'):
        prev=int(cgi.escape(self.request.get('prev')))
    else:
        prev = 1
    if self.request.get('next'): next=int(cgi.escape(self.request.get('next')))
    else: next = 4
    if self.request.get('day'): day=int(cgi.escape(self.request.get('day')))
    else: day = 0

    #mobiles = [
    #        'netfront',
    #        'midp',
    #        'iphone',
    #        'opera mobi'
    #        ]
    #mobile = 0
    #for x in mobiles:
    #    if x in self.request.headers["User-Agent"].lower(): mobile = 1

    #advanced_browsers = [
    #        'iphone',
    #        'opera mobi',
    #        ]
    #advanced = 0
    #for x in advanced_browsers:
    #    if x in self.request.headers["User-Agent"].lower(): advanced = 1

    #cant_do_ads = [
    #        'opera mobi',
    #        'midp',
    #        'netfront',
    #        ]
    #show_ads = 1
    #for x in cant_do_ads:
    #    if x in self.request.headers["User-Agent"].lower(): show_ads = 0

    debug = 0
    if self.request.get('debug'):
        debug = int(cgi.escape(self.request.get('debug')))

    useragent = self.request.headers["User-Agent"]
    mobile, supports_javascript, features = check_browscap(useragent)

    tn = datetime.datetime.utcnow() + datetime.timedelta(hours=2)
    timenow = tn.strftime('%A %H:%M')

    data, lastupdate = getfromDB(prevh=prev, nexth=next, dayh=day, highlight=hl)
    entries = countDB(Program())
    template_values = {
      'data': data,
      'entries': entries,
      'lastupdate': lastupdate,
      'uname': uname,
      'ulink': ulink,
      'mobile': mobile,
      'useragent': useragent,
      'features': features,
      'supports_javascript': supports_javascript,
      'timenow': timenow,
      #'show_ads': show_ads,
      #'advanced': advanced,
      'next': next,
      'debug': debug,
      }

    path = os.path.join(os.path.dirname(__file__), 'templates/index.html')
    self.response.out.write(template.render(path, template_values))
    #xmlstr = getxmltvlocal()
    #self.response.out.write(xmlstr)

class Reload(webapp.RequestHandler):
  def get(self):
    gogga = cgi.escape(self.request.get('gogga'))
    latest = get_lastupdated()
    if latest < datetime.datetime.utcnow() - datetime.timedelta(hours=6) or gogga == 'reloadnow':
        een = datetime.datetime.utcnow()
        try:
            num, deleted = putinDB()
            twee = datetime.datetime.utcnow()
            msg_page('Updated %d entries. Deleted %d entries. Previous update was at %s. (%s)'%(num, deleted, latest, twee-een), self)
        except apiproxy_errors.OverQuotaError, e:
            msg_page('Error updating database:<br />%s'%(e), self)
        except urlfetch.DownloadError, e:
            msg_page('Error updating database:<br />%s'%(e), self)

#    elif gogga == 'reloadbrowscap':
#        update_browscap()
#        msg_page('Updated browscap.ini:<br />%s'%('OK'), self)

    else:
        msg_page('Last updated %s. Not updating now.'%(latest), self)

class Clean(webapp.RequestHandler):
  def get(self):
    try:
        deleted, numProgramsbefore, numProgramsafter, count = cleanDB()
        self.response.out.write(str(deleted) +' | '+ str(numProgramsbefore) +' | '+ str(numProgramsafter) +' | '+ str(count))
    except DeadlineExceededError:
        msg_page('Error cleaning database:<br />Request timeout', self)
    except apiproxy_errors.OverQuotaError, e:
        msg_page('Error cleaning database:<br />%s'%(e), self)

class More(webapp.RequestHandler):
  def get(self):
    key = cgi.escape(self.request.get('key'))
    desc, titl = getdesc(key)
    template_values = {
      'desc': desc,
      'titl': titl,
      'key': key,
      }
    path = os.path.join(os.path.dirname(__file__), 'templates/more.html')
    self.response.out.write(template.render(path, template_values))

class Add(webapp.RequestHandler):
  def get(self):
    keyword = cgi.escape(self.request.get('keyword')).strip()

    user = users.get_current_user()
    if not user:
        self.redirect(users.create_login_url(self.request.uri))
    else:
        hl = UserHighlight.get_or_insert( user.email(), user=user, highlightlist=[keyword] ).highlightlist
        userhighl = UserHighlight( key_name=user.email(), user=user, highlightlist=list(set( hl+[keyword] )) )
        userhighl.put()
        #TODO this is just temporary - get a proper template
        template_values = {
          'desc': keyword,
          'titl': keyword,
          }
        path = os.path.join(os.path.dirname(__file__), 'templates/add.html')
        self.response.out.write(template.render(path, template_values))

class Search(webapp.RequestHandler):
  def post(self):
    search=cgi.escape(self.request.get('search'))
    data, lastupdate = getfromDB(search=search)
    entries = countDB(Program())

    useragent = self.request.headers["User-Agent"]
    mobile, supports_javascript, features = check_browscap(useragent)

    template_values = {
      'data': data,
      'search': search,
      'entries': entries,
      'lastupdate': lastupdate,
      'mobile': mobile,
      'useragent': useragent,
      'features': features,
      'supports_javascript': supports_javascript,
      }
    path = os.path.join(os.path.dirname(__file__), 'templates/index.html')
    self.response.out.write(template.render(path, template_values))
    #self.response.out.write(cgi.escape(self.request.get('search')))


application = webapp.WSGIApplication(
                                     [('/', MainPage),
                                        ('/reload', Reload),
                                        ('/search', Search),
                                        ('/clean', Clean),
                                        ('/more', More),
                                        ('/add', Add),
                                        ],
                                     debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()
